import rough from 'roughjs'
import { select } from 'd3-selection'
import markerShape from '../marker-shape'
import lineShapeMap from '../line-shape'

let container = null
let rc = null
export const edgeRoughOptions = {
  fillStyle: 'hachure',
  seed: 88,
  fillWeight: 1,
  hachureGap: 1,
  bowing: 1.2,
  roughness: 0.5
}

export function graphEdgeContainer (mindContainer) {
  rc = rough.svg(mindContainer)
  container = mindContainer
    .insert('g', '.mind-map-nodebox')
    .attr('class', 'mind-map-edgebox')
  return container
}

export function graphNewAllElementEdge (links) {
  const enter = container
    .selectAll('g')
    .data(links)
    .enter()
  const isRough = Boolean(Number(sessionStorage.getItem('isRough')))
  if (isRough) {
    graphNewRoughEdges(enter)
  } else {
    graphNewEdges(enter)
  }
}

/**
 * 绘制新增的手绘效果边数据
 * @param {*} enter
 */
function graphNewRoughEdges (enter) {
  enter.append(d => {
    const { sourcePoint, targetPoint, gradient, strokeWidth, lineWidth, direction, lineStyleValue, fill } = generateEdgePathMetadata(d)
    const node = rc.path(lineShapeMap[lineStyleValue]({
      sourcePoint,
      targetPoint,
      gradient: typeof d.source.style.lineStyle.lineWidth === 'string',
      lineWidth,
      direction
    }), {
      stroke: d.source.data.isRoot ? (d.source.style.lineStyle.lineColor || d.target.style.lineStyle.fill) : d.source.style.lineStyle.fill,
      fill,
      strokeWidth: gradient ? 0.2 : strokeWidth,
      ...edgeRoughOptions
    })
    select(node).select('path')
      .attr('marker-end', () => {
        const sourceIsRoot = d.source.data.isRoot
        return d.source.style.lineStyle.lineEndJoin ? `url(#mark-triangle-${d[sourceIsRoot ? 'target' : 'source'].data._id})` : null
      })
    return node
  })
    .each(function (d) {
      const { sourcePoint, targetPoint, gradient, lineWidth, strokeWidth, direction, lineStyleValue, braceAndOnlyOneChild, fill } = generateEdgePathMetadata(d)
      if (braceAndOnlyOneChild) {
        const node = select(rc.path(lineShapeMap[lineStyleValue]({
          sourcePoint,
          targetPoint: {
            tx: targetPoint.tx,
            ty: targetPoint.ty + d.target.height
          },
          gradient: typeof d.source.style.lineStyle.lineWidth === 'string',
          lineWidth,
          direction
        }), {
          stroke: d.source.data.isRoot ? (d.source.style.lineStyle.lineColor || d.target.style.lineStyle.fill) : d.source.style.lineStyle.fill,
          fill,
          strokeWidth: gradient ? 0.2 : strokeWidth,
          ...edgeRoughOptions
        })).select('path').attr('class', 'clone-edge')
        select(this).append(() => {
          return node.node()
        })
      }
    })
}

function graphNewEdges (enter) {
  enter
    .append('g')
    .append('path')
    .attr('d', function (d) {
      const { sourcePoint, targetPoint, gradient, lineWidth, direction, lineStyleValue } = generateEdgePathMetadata(d)
      return lineShapeMap[lineStyleValue]({
        sourcePoint,
        targetPoint,
        gradient,
        lineWidth,
        direction
      })
    })
    .attr('stroke-linecap', d => d.source.style.lineStyle.lineCap || 'round')
    .attr('stroke', d => {
      if (d.source.data.isRoot) {
        return d.source.style.lineStyle.lineColor || d.target.style.lineStyle.fill
      }
      return d.source.style.lineStyle.fill
    })
    .attr('stroke-width', d => {
      let lineWidth = d.source.style.lineStyle.lineWidth
      lineWidth = typeof lineWidth === 'number' ? lineWidth : Number(lineWidth.split('-')[1])
      const gradient = typeof d.source.style.lineStyle.lineWidth === 'string'
      if (gradient) return 0.2
      return lineWidth
    })
    .attr('stroke-dasharray', d => d.source.style.lineStyle.dot === 'dashed' ? '5, 5' : undefined)
    .attr('fill', d => {
      const gradient = typeof d.source.style.lineStyle.lineWidth === 'string'
      if (gradient) {
        if (d.source.data.isRoot) {
          return d.source.style.lineStyle.lineColor || d.target.style.lineStyle.fill
        }
        return d.source.style.lineStyle.fill
      }
      return 'none'
    })
    .attr('marker-end', d => {
      const sourceIsRoot = d.source.data.isRoot
      return d.source.style.lineStyle.lineEndJoin ? `url(#mark-triangle-${d[sourceIsRoot ? 'target' : 'source'].data._id})` : null
    })
    .each(function (d) {
      const { sourcePoint, targetPoint, gradient, lineWidth, direction, lineStyleValue, braceAndOnlyOneChild } = generateEdgePathMetadata(d)
      if (braceAndOnlyOneChild) {
        select(this).clone().attr('class', 'clone-edge').attr('d', () => {
          return lineShapeMap[lineStyleValue]({
            sourcePoint,
            targetPoint: {
              tx: targetPoint.tx,
              ty: targetPoint.ty + d.target.height
            },
            gradient,
            lineWidth,
            direction
          })
        })
      }
    })
}

export function updateExistAllElementEdge (links) {
  const isRough = Boolean(Number(sessionStorage.getItem('isRough')))
  if (isRough) {
    updateExistRoughEdges(links)
  } else {
    updateExistEdges(links)
  }
}

function updateExistRoughEdges (links) {
  container
    .selectAll('g')
    .data(links)
    .html(d => {
      const { sourcePoint, targetPoint, gradient, lineWidth, direction, lineStyleValue, fill } = generateEdgePathMetadata(d)
      const strokeWidth = typeof lineWidth === 'number' ? lineWidth : Number(lineWidth.split('-')[1])
      const node = select(rc.path(lineShapeMap[lineStyleValue]({
        sourcePoint,
        targetPoint,
        gradient: typeof d.source.style.lineStyle.lineWidth === 'string',
        lineWidth,
        direction
      }), {
        stroke: d.source.data.isRoot ? (d.source.style.lineStyle.lineColor || d.target.style.lineStyle.fill) : d.source.style.lineStyle.fill,
        fill,
        strokeWidth: gradient ? 0.2 : strokeWidth,
        ...edgeRoughOptions
      }))
      node.select('path')
        .attr('marker-end', () => {
          const sourceIsRoot = d.source.data.isRoot
          return d.source.style.lineStyle.lineEndJoin ? `url(#mark-triangle-${d[sourceIsRoot ? 'target' : 'source'].data._id})` : null
        })
      return node.html()
    })
    .each(function (d) {
      select(this).select('.clone-edge').remove()
      const { sourcePoint, targetPoint, gradient, lineWidth, strokeWidth, direction, lineStyleValue, braceAndOnlyOneChild, fill } = generateEdgePathMetadata(d)
      if (braceAndOnlyOneChild) {
        const node = select(rc.path(lineShapeMap[lineStyleValue]({
          sourcePoint,
          targetPoint: {
            tx: targetPoint.tx,
            ty: targetPoint.ty + d.target.height
          },
          gradient: typeof d.source.style.lineStyle.lineWidth === 'string',
          lineWidth,
          direction
        }), {
          stroke: d.source.data.isRoot ? (d.source.style.lineStyle.lineColor || d.target.style.lineStyle.fill) : d.source.style.lineStyle.fill,
          fill,
          strokeWidth: gradient ? 0.2 : strokeWidth,
          ...edgeRoughOptions
        })).select('path').attr('class', 'clone-edge')
        select(this).append(() => {
          return node.node()
        })
      }
    })
}

function updateExistEdges (links) {
  container
    .selectAll('g')
    .data(links)
    .select('path')
    .attr('d', d => {
      const { sourcePoint, targetPoint, gradient, lineWidth, direction, lineStyleValue } = generateEdgePathMetadata(d)
      return lineShapeMap[lineStyleValue]({
        sourcePoint,
        targetPoint,
        gradient,
        lineWidth,
        direction
      })
    })
    .attr('stroke-linecap', d => d.source.style.lineStyle.lineCap || 'round')
    .attr('stroke', d => {
      if (d.source.data.isRoot) {
        return d.source.style.lineStyle.lineColor || d.target.style.lineStyle.fill
      }
      return d.source.style.lineStyle.fill
    })
    .attr('stroke-width', d => {
      let lineWidth = d.source.style.lineStyle.lineWidth
      lineWidth = typeof lineWidth === 'number' ? lineWidth : Number(lineWidth.split('-')[1])
      const gradient = typeof d.source.style.lineStyle.lineWidth === 'string'
      if (gradient) return 0.2
      return lineWidth
    })
    .attr('stroke-dasharray', d => d.source.style.lineStyle.dot === 'dashed' ? '5, 5' : undefined)
    .attr('fill', d => {
      const gradient = typeof d.source.style.lineStyle.lineWidth === 'string'
      if (gradient) {
        if (d.source.data.isRoot) {
          return d.source.style.lineStyle.lineColor || d.target.style.lineStyle.fill
        }
        return d.source.style.lineStyle.fill
      }
      return 'none'
    })
    .attr('marker-end', d => {
      const sourceIsRoot = d.source.data.isRoot
      return d.source.style.lineStyle.lineEndJoin ? `url(#mark-triangle-${d[sourceIsRoot ? 'target' : 'source'].data._id})` : null
    })
    .each(function (d) {
      select(this.parentNode).select('.clone-edge').remove()
      const { sourcePoint, targetPoint, gradient, lineWidth, direction, lineStyleValue, braceAndOnlyOneChild } = generateEdgePathMetadata(d)
      if (braceAndOnlyOneChild) {
        select(this).clone().attr('class', 'clone-edge').attr('d', () => {
          return lineShapeMap[lineStyleValue]({
            sourcePoint,
            targetPoint: {
              tx: targetPoint.tx,
              ty: targetPoint.ty + d.target.height
            },
            gradient,
            lineWidth,
            direction
          })
        })
      }
    })
}

export function deleteSuperEdges (links) {
  container
    .selectAll('g')
    .data(links)
    .exit()
    .remove()
}

/**
 * 根据source和target生成edge元数据信息
 * @param {*} d
 * @returns
 */
function generateEdgePathMetadata (d) {
  let fill = 'none'
  const lineStyleValue = d.source.style.lineStyle.branch
  const braceAndOnlyOneChild = lineStyleValue === 'lineBracket' && d.target.parent.children.length === 1 // 连线的样式为括号图并且只有一个子节点的时候
  const lineWidth = d.source.style.lineStyle.lineWidth
  const sourceSpacing = ['underline', 'doubleUnderline'].includes(d.source.style.shape) && !braceAndOnlyOneChild ? d.source.height / 2 : 0
  const targetSpacing = ['underline', 'doubleUnderline'].includes(d.target.style.shape) && !braceAndOnlyOneChild ? d.target.height / 2 : 0
  const sx = d.target.direction === 'left'
    ? d.source.x + d.source.gap
    : d.target.direction === 'right'
      ? d.source.width + d.source.x - d.source.gap
      : d.source.x + d.source.width / 2
  const sourcePoint = {
    sx,
    sy: d.target.direction === 'bottom'
      ? d.source.y + d.source.height
      : d.source.height / 2 + d.source.y + sourceSpacing
  }
  const distance = markerShape[d.source.style.lineStyle.lineEndJoin || 'arrow-none'].markerWidth * (typeof lineWidth === 'string' ? 1 : lineWidth)
  const targetPoint = {
    tx: d.target.direction === 'left'
      ? d.target.x + d.target.width + distance
      : d.target.direction === 'right'
        ? d.target.x - distance
        : d.target.x + d.target.width / 2,
    ty: d.target.direction === 'bottom'
      ? d.target.y - distance
      : (d.target.height / 2 + d.target.y + targetSpacing - (braceAndOnlyOneChild ? d.target.height / 2 : 0))
  }
  if (braceAndOnlyOneChild && ['underline', 'doubleUnderline'].includes(d.source.style.shape) && d.target.height < d.source.height) {
    sourcePoint.sy = targetPoint.ty + d.target.height / 2
  }
  const gradient = typeof d.source.style.lineStyle.lineWidth === 'string'
  if (gradient) {
    if (d.source.data.isRoot) {
      fill = d.source.style.lineStyle.lineColor || d.target.style.lineStyle.fill
    } else {
      fill = d.source.style.lineStyle.fill
    }
  }
  return {
    sourcePoint,
    targetPoint,
    fill,
    gradient,
    lineWidth,
    strokeWidth: typeof lineWidth === 'number' ? lineWidth : Number(lineWidth.split('-')[1]),
    direction: d.target.direction,
    lineStyleValue,
    braceAndOnlyOneChild
  }
}
